package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/boltdb/bolt"
	"github.com/kpango/glg"
)

type Store interface {
	GetAddrs() (string, error)
	GetAddrsLimited(string, string, string, string) (string, error)
	GetSources(string) (string, error)
	GetStatesArr(string, string) (string, error)
	GetVersionsMap(string, string) (string, error)
	GetTimes(string, string, string) (string, error)
	GetStatesCount(int) (string, error)
	Remove(string, string) error
	BatchPut([][]string)
	Close() error
}

func NewStore(basepath, dbname string) (s Store, err error) {
	s, err = NewBoltStore(basepath, dbname)
	return
}

type BoltStore struct {
	fn string
	db *bolt.DB
}

func NewBoltStore(basepath, dbname string) (Store, error) {
	_, err := os.Stat(basepath)
	if os.IsNotExist(err) {
		err = os.MkdirAll(basepath, 0755)
	}
	if err != nil {
		return nil, err
	}
	fn := path.Join(basepath, dbname)
	db, err := bolt.Open(fn, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltStore{
		fn: fn, db: db,
	}, nil
}

func (b *BoltStore) Remove(ip, removed string) error {
	var bucket *bolt.Bucket
	var err error
	str := ""
	msg := ""
	return b.db.Update(func(tx *bolt.Tx) error {
		if bucket = tx.Bucket([]byte(ip)); bucket == nil {
			if removed == "true" {
				str = "Remove"
			} else {
				str = "Recover removed"
			}
			msg = "%s  machine error: ip %s not found\n"
			glg.Errorf(msg, str, ip)
			return fmt.Errorf("ip not found")
		}
		c := bucket.Cursor()
		for source, _ := c.First(); source != nil; source, _ = c.Next() {
			sourceBkt := bucket.Bucket(source)
			if removed == "true" {
				if err = sourceBkt.Put([]byte("remove"), []byte("true")); err != nil {
					return err
				}
			} else if removed == "false" {
				if err = sourceBkt.Delete([]byte("remove")); err != nil {
					return err
				}
			}
		}
		msg := "%s machine %s success.\n"
		glg.Infof(msg, str, ip)
		return nil
	})
}

func (b *BoltStore) BatchPut(data [][]string) {
	var bucket *bolt.Bucket
	var err error
	b.db.Batch(func(tx *bolt.Tx) error {
		for _, req := range data {
			// req[0]:addr, req[1]:source, req[2]:state, req[3]:timestamp
			if len(req) != 4 {
				glg.Errorf("Fail to write data(%v) to boltdb: wrong data length.\n", req)
				continue
			}
			if bucket, err = tx.CreateBucketIfNotExists([]byte(req[0])); err != nil {
				glg.Errorf("Fail to write data to boltdb, addr:%v, source:%v, state:%v. Error detail: %v\n",
					req[0], req[1], req[2], err)
				continue
			}
			if bucket, err = bucket.CreateBucketIfNotExists([]byte(req[1])); err != nil {
				glg.Errorf("Fail to write data to boltdb, addr:%v, source:%v, state:%v. Error detail: %v\n",
					req[0], req[1], req[2], err)
				continue
			}
			str := "Update"
			if initTimestamp := bucket.Get([]byte(req[2] + ":init")); initTimestamp == nil {
				if err = bucket.Put([]byte(req[2]+":init"), []byte(req[3])); err != nil {
					glg.Errorf("Fail to write data to boltdb, addr:%v, source:%v, state:%v. Error detail: %v\n",
						req[0], req[1], req[2], err)
					continue
				}
				str = "Create"
			}
			if err = bucket.Put([]byte(req[2]+":last"), []byte(req[3])); err != nil {
				glg.Errorf("Fail to write data to boltdb, addr:%v, source:%v, state:%v. Error detail: %v\n",
					req[0], req[1], req[2], err)
				continue
			}
			msg := "%s heartbeat timestamp success. addr: %s, source: %s, state: %s\n"
			glg.Infof(msg, str, req[0], req[1], req[2])
		}
		return nil
	})
}

type GetStatesCountRet struct {
	Addr  string
	Count int
}

type CountList []GetStatesCountRet

func (p CountList) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func (p CountList) Len() int {
	return len(p)
}

func (p CountList) Less(i, j int) bool {
	return p[i].Count < p[j].Count
}

func (b *BoltStore) GetStatesCount(interval int) (string, error) {
	ret := make(map[string]int)
	var err error
	err = b.db.View(func(tx *bolt.Tx) error {
		return tx.ForEach(func(addr []byte, bucket *bolt.Bucket) error {
			addrCur := bucket.Cursor()
			for source, _ := addrCur.First(); source != nil; source, _ = addrCur.Next() {
				sourceBkt := bucket.Bucket(source)
				sourceCur := sourceBkt.Cursor()
				for key, val := sourceCur.First(); key != nil; key, val = sourceCur.Next() {
					if strings.HasSuffix(string(key), ":last") {
						now := time.Now()
						loc, _ := time.LoadLocation("Local")
						last, _ := time.ParseInLocation("2006-01-02 15:04:05", string(val), loc)
						delta := int(now.Sub(last).Seconds())
						if delta <= interval {
							ret[string(addr)]++
						}
					}
				}
			}
			return nil
		})
	})
	i := 0
	countList := make(CountList, len(ret))
	for k, v := range ret {
		countList[i] = GetStatesCountRet{k, v}
		i++
	}
	sort.Sort(countList)

	if err != nil {
		return "", err
	}

	show, _ := json.MarshalIndent(&countList, "", "  ")
	return string(show), nil
}

func (b *BoltStore) GetAddrs() (string, error) {
	var ret []string
	var err error
	err = b.db.View(func(tx *bolt.Tx) error {
		return tx.ForEach(func(name []byte, b *bolt.Bucket) error {
			ret = append(ret, string(name))
			return nil
		})
	})
	if err != nil {
		return "", err
	}
	show, _ := json.MarshalIndent(&ret, "", "  ")
	return string(show), nil
}

func (b *BoltStore) GetSources(addr string) (string, error) {
	var ret []string
	var bucket *bolt.Bucket
	var err error
	err = b.db.View(func(tx *bolt.Tx) error {
		if bucket = tx.Bucket([]byte(addr)); bucket == nil {
			glg.Errorf("Get sources error: addr %s not found.\n", addr)
			return Status404NotFoundErr
		}
		c := bucket.Cursor()
		for k, _ := c.First(); k != nil; k, _ = c.Next() {
			ret = append(ret, string(k))
		}
		return nil
	})
	if err != nil {
		return "", err
	}
	show, _ := json.MarshalIndent(&ret, "", "  ")
	return string(show), nil
}

type GetAddrRet struct {
	Addr   string
	Ver    string
	Time   int
	sortby string
}

type GetAddrRetList []GetAddrRet

func (p GetAddrRetList) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func (p GetAddrRetList) Len() int {
	return len(p)
}

func (p GetAddrRetList) Less(i, j int) bool {
	if p[i].sortby == "time" {
		return p[i].Time < p[j].Time
	} else {
		return verCompare(p[i].Ver, p[j].Ver) == -1
	}
}

func (b *BoltStore) GetAddrsLimited(ver, vercmp, death, t string) (string, error) {
	ret := make(map[string]map[string]int)
	var err error
	// 最近一次请求的状态和时长
	var res_state string
	var res_time int
	// 当前请求的状态和时长
	var cur_state string
	var cur_time int
	err = b.db.View(func(tx *bolt.Tx) error {
		return tx.ForEach(func(name []byte, b *bolt.Bucket) error {
			res_state = ""
			res_time = 0
			ipcursor := b.Cursor()
			for source, _ := ipcursor.First(); source != nil; source, _ = ipcursor.Next() {
				sourceBkt := b.Bucket(source)
				sourceCur := sourceBkt.Cursor()
				// 死亡机器，忽略拆机
				if sourceBkt.Get([]byte("remove")) != nil && death == "down" {
					continue
				}
				for key, val := sourceCur.First(); key != nil; key, val = sourceCur.Next() {
					if strings.HasSuffix(string(key), ":last") {
						cur_state = strings.Split(string(key), ":")[0]
						now := time.Now()
						loc, _ := time.LoadLocation("Local")
						last, _ := time.ParseInLocation("2006-01-02 15:04:05", string(val), loc)
						delta := int(now.Sub(last).Seconds())
						cur_time = delta
						if res_state == "" || res_time > cur_time {
							res_state = cur_state
							res_time = cur_time
						}
					}
				}
			}
			date := strings.Split(res_state, ".")[1]
			v := strings.Split(res_state, ".")[2]
			res_version := date + "." + v
			drop := false
			if ver != "" {
				if verCompare(res_version, ver) == -2 {
					glg.Errorf("Get machine error: unsupport value of ver %s\n", ver)
					return ReqParametersError
				}
				if vercmp == "older" {
					if verCompare(res_version, ver) >= 0 {
						drop = true
					}
				} else if vercmp == "equal" {
					if verCompare(res_version, ver) != 0 {
						drop = true
					}
				} else if vercmp == "newer" {
					if verCompare(res_version, ver) < 0 {
						drop = true
					}
				} else {
					return ReqParametersError
				}

			}
			if death == "down" {
				timedeath, err := strconv.Atoi(t)
				if err != nil {
					glg.Errorf("Get machine error: unable to parse para 'time' to int: %s", t)
					return err
				}
				if res_time < timedeath {
					drop = true
				}
			} else if death == "work" {
				timedeath, err := strconv.Atoi(t)
				if err != nil {
					glg.Errorf("Get machine error: unable to parse para 'time' to int: %s", t)
					return err
				}
				if res_time >= timedeath {
					drop = true
				}
			} else if death != "" {
				return ReqParametersError
			}

			if !drop {
				if ret[string(name)] == nil {
					ret[string(name)] = make(map[string]int)
				}
				ret[string(name)][res_version] = res_time
			}

			return nil
		})
	})

	i := 0
	p := make(GetAddrRetList, len(ret))
	for ip, vmap := range ret {
		for ver, time := range vmap {
			if death != "" {
				p[i] = GetAddrRet{ip, ver, time, "time"}
			} else {
				p[i] = GetAddrRet{ip, ver, time, "version"}
			}
			i = i + 1
		}
	}

	sort.Sort(p)

	if err != nil {
		return "", err
	}
	show, _ := json.MarshalIndent(&p, "", "  ")
	return string(show), nil
}

/*
version compare:
	return  1: version v1 newer than v2
	return  0: version v1 equals v2
	return -1: version v1 older v2
*/
func verCompare(ver1, ver2 string) int {
	if len(strings.Split(ver1, ".")) < 2 || len(strings.Split(ver2, ".")) < 2 {
		return -2
	}
	d1 := strings.Split(ver1, ".")[0]
	v1 := strings.Split(ver1, ".")[1]
	d2 := strings.Split(ver2, ".")[0]
	v2 := strings.Split(ver2, ".")[1]
	t1, err1 := time.Parse("20060102", d1)
	t2, err2 := time.Parse("20060102", d2)
	num1, err3 := strconv.Atoi(v1)
	num2, err4 := strconv.Atoi(v2)
	if err1 != nil || err2 != nil || err3 != nil || err4 != nil {
		return -2
	}
	if t1.After(t2) || (t1.Equal(t2) && num1 > num2) {
		return 1
	} else if t1.Equal(t2) && num1 == num2 {
		return 0
	} else {
		return -1
	}
}

func (b *BoltStore) GetStatesArr(addr, source string) (string, error) {
	var ret []string
	var bucket *bolt.Bucket
	var err error
	err = b.db.View(func(tx *bolt.Tx) error {
		if bucket = tx.Bucket([]byte(addr)); bucket == nil {
			glg.Errorf("Get states error: addr %s not found.", addr)
			return Status404NotFoundErr
		}
		if bucket = bucket.Bucket([]byte(source)); bucket == nil {
			glg.Errorf("Get states error: source %s not found.", source)
			return Status404NotFoundErr
		}
		c := bucket.Cursor()
		for k, _ := c.First(); k != nil; k, _ = c.Next() {
			if string(k[len(k)-4:]) == "init" {
				state := k[:len(k)-5]
				ret = append(ret, string(state))
			}
		}
		return nil
	})
	if err != nil {
		return "", err
	}
	show, _ := json.MarshalIndent(&ret, "", "  ")
	return string(show), nil
}

func (b *BoltStore) GetVersionsMap(addr, source string) (string, error) {
	ret := make(map[string]time.Time)
	var bucket *bolt.Bucket
	var err error
	err = b.db.View(func(tx *bolt.Tx) error {
		if bucket = tx.Bucket([]byte(addr)); bucket == nil {
			glg.Errorf("Get versions error: addr %s not found.", addr)
			return Status404NotFoundErr
		}
		if bucket = bucket.Bucket([]byte(source)); bucket == nil {
			glg.Errorf("Get versions error: source %s not found.", source)
			return Status404NotFoundErr
		}
		c := bucket.Cursor()
		for k, val := c.First(); k != nil; k, val = c.Next() {
			if string(k[len(k)-4:]) == "last" {
				state := k[:len(k)-5]
				date := strings.Split(string(state), ".")[1]
				v := strings.Split(string(state), ".")[2]
				version := date + "." + v
				// Time Loc: CST
				loc, _ := time.LoadLocation("Local")
				last, _ := time.ParseInLocation("2006-01-02 15:04:05", string(val), loc)
				_, exist := ret[version]
				if !exist {
					ret[version] = last
				} else if ret[version].Before(last) {
					ret[version] = last
				}
			}
		}
		return nil
	})
	if err != nil {
		return "", err
	}
	show, _ := json.MarshalIndent(&ret, "", "  ")
	return string(show), nil
}

func (b *BoltStore) GetTimes(addr, source, state string) (string, error) {
	ret := make(map[string]map[string]map[string]map[string]time.Time)
	var err error
	err = b.db.View(func(tx *bolt.Tx) error {
		return tx.ForEach(func(name []byte, bucket *bolt.Bucket) error {
			match, _ := regexp.MatchString(addr, string(name))
			if match {
				sourceCur := bucket.Cursor()
				for s, _ := sourceCur.First(); s != nil; s, _ = sourceCur.Next() {
					match, _ = regexp.MatchString(source, string(s))
					if match {
						sourceBkt := bucket.Bucket(s)
						c := sourceBkt.Cursor()
						for k, val := c.First(); k != nil; k, val = c.Next() {
							if strings.HasSuffix(string(k), ":init") {
								curstate := strings.TrimRight(string(k), ":init")
								match, _ = regexp.MatchString(state, curstate)
								if match {
									date := strings.Split(curstate, ".")[1]
									v := strings.Split(curstate, ".")[2]
									version := date + "." + v
									loc, _ := time.LoadLocation("Local")
									init, _ := time.ParseInLocation("2006-01-02 15:04:05", string(val), loc)
									if _, exist := ret[string(name)]; !exist {
										ret[string(name)] = make(map[string]map[string]map[string]time.Time)
									}
									if _, exist := ret[string(name)][string(s)]; !exist {
										ret[string(name)][string(s)] = make(map[string]map[string]time.Time)
									}
									if _, exist := ret[string(name)][string(s)][version]; !exist {
										ret[string(name)][string(s)][version] = make(map[string]time.Time)
									}
									if _, exist := ret[string(name)][string(s)][version]["init"]; !exist {
										ret[string(name)][string(s)][version]["init"] = init
									} else if ret[string(name)][string(s)][version]["init"].After(init) {
										ret[string(name)][string(s)][version]["init"] = init
									}
								}
							} else if strings.HasSuffix(string(k), ":last") {
								curstate := strings.TrimRight(string(k), ":last")
								match, _ = regexp.MatchString(state, curstate)
								if match {
									date := strings.Split(curstate, ".")[1]
									v := strings.Split(curstate, ".")[2]
									version := date + "." + v
									loc, _ := time.LoadLocation("Local")
									last, _ := time.ParseInLocation("2006-01-02 15:04:05", string(val), loc)
									if _, exist := ret[string(name)]; !exist {
										ret[string(name)] = make(map[string]map[string]map[string]time.Time)
									}
									if _, exist := ret[string(name)][string(s)]; !exist {
										ret[string(name)][string(s)] = make(map[string]map[string]time.Time)
									}
									if _, exist := ret[string(name)][string(s)][version]; !exist {
										ret[string(name)][string(s)][version] = make(map[string]time.Time)
									}
									if _, exist := ret[string(name)][string(s)][version]["last"]; !exist {
										ret[string(name)][string(s)][version]["last"] = last
									} else if ret[string(name)][string(s)][version]["last"].After(last) {
										ret[string(name)][string(s)][version]["last"] = last
									}
								}
							}
						}
					}
				}
			}
			return nil
		})
	})

	if err != nil {
		return "", err
	}

	show, _ := json.MarshalIndent(&ret, "", "  ")
	return string(show), nil
}

func (b *BoltStore) Close() error {
	return b.db.Close()
}
